/**
 * @description 给元素绑定事件处理函数
 * @param {HTMLElement} elem  绑定事件处理函数的元素对象
 * @param {String} type  所要绑定的事件名称
 * @param {Function} handle  所绑定的事件处理函数
 * @returns { undefined } 没有返回值
 */
function addEvent(elem, type, handle) {
  if (elem.addEventListener) {
    elem.addEventListener(type, handle, false);
  } else if (elem.attachEvent) {
    elem.attachEvent('on' + type, handle.bind(elem));
  } else {
    elem['on' + type] = handle;
  }
}

/**
 * @description 给元素解除绑定事件处理函数
 * @param {HTMLElement} elem  解除绑定事件处理函数的元素对象
 * @param {String} type  所要解除绑定的事件名称
 * @param {Function} handle  所解除绑定的事件处理函数
 * @returns { undefined } 没有返回值
 */
function removeEvent(elem, type, handle) {
  if (elem.removeEventListener) {
    elem.removeEventListener(type, handle, false);
  } else if (elem.detachEvent) {
    elem.detachEvent('on' + type, handle);
  } else {
    elem['on' + type] = null;
  }
}

/**
 * @description 获取元素下的元素子节点集合
 * @param {HTMLElement} elem 需要获取子元素集合的目标父节点
 * @returns { HTMLElement[] } 返回子元素集合
 */
function elemChildren(elem) {
  var obj = {
      length: 0,
      splice: Array.prototype.splice,
      push: Array.prototype.push,
    },
    children = elem.childNodes,
    len = children.length,
    item;

  for (var i = 0; i < len; i++) {
    item = children[i];
    if (item.nodeType === 1) {
      obj.push(item);
    }
  }

  return obj;
}

/**
 * @description 在原型上封装向目标元素后添加新节点的方法
 * @param { HTMLElement } node 添加在目标元素后的新元素节点
 * @param { HTMLElement } target 添加元素的目标元素节点
 * @returns { undefined } 无返回值
 */
Node.prototype.insertAfter = function (node, target) {
  var parentNode = this;

  if (parentNode.lastChild === target) {
    parentNode.appendChild(node);
  } else {
    parentNode.insertBefore(node, target.nextSibling);
  }
};

/**
 * @description 封装获取滚动条滚动距离的兼容性方法
 * @returns { {left: Number, top: Number}} 返回滚动条距离信息对象
 */
function getScrollOffset() {
  if (window.pageXOffset) {
    return {
      left: window.pageXOffset,
      top: window.pageYOffset,
    };
  } else {
    return {
      left: document.body.scrollLeft + document.documentElement.scrollLeft,
      top: document.body.scrollTop + document.documentElement.scrollTop,
    };
  }
}

/**
 * @description 封装获取页面真实宽度/高度(可视区域)信息的方法
 * @returns { { width: Number, height: Number} } 返回页面真实高宽信息对象
 */
function getViewSize() {
  if (window.innerWidth) {
    return {
      width: window.innerWidth,
      height: window.innerHeight,
    };
  } else {
    // HTML的模式: 1. CSS1Compat 标准模式 2. BackCompat 怪异模式
    // 在标准模式下获取页面的可视尺寸信息得通过documentElement也就是html对象获取
    // 而在怪异模式下, 则是通过body获取
    return document.compatMode === 'BackCompat'
      ? {
          width: document.body.clientWidth,
          height: document.body.clientHeight,
        }
      : {
          width: document.documentElement.clientWidth,
          height: document.documentElement.clientHeight,
        };
  }
}

/**
 * @description 获取页面完整高度/高度的方法(页面高度 = 滚动条Y轴滚动位置 + 页面真实高度)
 * @returns { { width: Number, height: Number} } 返回页面完整高度宽度对象信息
 */
function getScrollSize() {
  if (document.body.scrollWidth) {
    return {
      width: document.body.scrollWidth,
      height: document.body.scrollHeight,
    };
  } else {
    return {
      width: document.documentElement.scrollWidth,
      height: document.documentElement.scrollHeight,
    };
  }
}

/**
 * @description 获取元素距离页面最左侧、最顶端的距离
 * @param { HTMLElement } el 目标元素节点
 * @returns { { left: Number, top: Number} } 返回元素距离文档左侧、顶部的距离信息
 */
function getElemDocPosition(el) {
  // HTMLElement.prototype.offsetParent方法可以返回调用元素的外层具有定位属性的元素节点
  // 直到找到body元素
  var parent = el.offsetParent,
    offsetLeft = el.offsetLeft,
    offsetTop = el.offsetTop;
  // 迭代累加元素距离各父级之间的距离
  while (parent) {
    offsetLeft += parent.offsetLeft;
    offsetTop += parent.offsetTop;
    parent = parent.offsetParent;
  }
  return {
    left: offsetLeft,
    top: offsetTop,
  };
}
/**
 * @description 递归写法
 */
function getElemDocPosition(el) {
  // HTMLElement.prototype.offsetParent方法可以返回调用元素的外层具有定位属性的元素节点
  // 直到找到body元素
  var parent = el.offsetParent,
    offsetLeft = el.offsetLeft,
    offsetTop = el.offsetTop;
  // 迭代累加元素距离各父级之间的距离
  if (parent) {
    offsetLeft += getElemDocPosition(parent).left;
    offsetTop += getElemDocPosition(parent).top;
  }
  return {
    left: offsetLeft,
    top: offsetTop,
  };
}

/**
 * @description 获取元素属性方法的兼容性方法
 * @param { HTMLElement } elem 目标元素
 * @param { String } prop 获取的CSS属性名
 */
function getStyles(elem, prop) {
  if (window.getComputedStyle) {
    return prop
      ? window.getComputedStyle(elem, null)[prop]
      : window.getComputedStyle(elem);
  } else {
    return prop ? elem.currentStyle[prop] : elem.currentStyle;
  }
}

/**
 * @description 返回元素对应层级的父级元素节点
 * @param { HTMLElement } elem 目标元素
 * @param { Number } hierarchy 查询的父级元素层级
 */
function elemParent(elem, hierarchy) {
  var type = typeof hierarchy;
  if (type === 'undefined') {
    // 若没有传入第二个参数, 默认返回一层父级元素节点
    return elem.parentNode;
  } else if (hierarchy <= 0 || type !== 'number') {
    // 传入非法值, 返回undefined
    return undefined;
  }

  while (hierarchy) {
    elem = elem.parentNode;
    hierarchy--;
  }

  return elem;
}

/**
 * @description 获取一个元素上第N层的父级元素节点
 * @param { HTMLElement } elem 
 */
function elemParent(elem, hierarchy) {
  var type = typeof hierarchy,
    parent = elem;
  
  if (type === 'undefined' || type !== 'number') {
    return parent.parentNode 
    ? parent.parentNode
    : parent;
  } else if (hierarchy < 0) {
    return undefined;
  } else {
    while (hierarchy) {
      parent.parentNode && (parent = parent.parentNode);
      hierarchy--;
    }
    return parent;
  }
}

/**
 * @description 时间格式化
 * @returns 格式化后的时间字符串
 */
Date.prototype.getDateTime = function() {
  var year = this.getFullYear(),
    month = this.getMonth() + 1,
    day = this.getDate(),
    hours = this.getHours(),
    minutes = this.getMinutes(),
    seconds = this.getSeconds();
  return year + '-' + format(month) + '-' + format(day) + ' ' + format(hours) + ':' +
    format(minutes) + ':' + format(seconds);

  function format(time) {
    return time < 0 ? '0' + time : time;
  }
}

/**
 * 
 * @param {Event} e 事件对象
 */
function cancelBubble(e) {
  var e = e || window.event;
  if (e.stopPropagation) {
    e.stopPropagation();
  } else {
    e.cancelBubble = true;
  }
}

/**
 * 
 * @param {Event} e 事件对象
 */
function preventDefaultEvent(e) {
  var e = e || window.event;
  if (e.preventDefault) {
    e.preventDefault();
  } else {
    e.returnValue = false;
  }
}

/**
 * 
 * @param {Event} e 事件对象
 * @returns 
 */
function pagePos(e) {
  var e = e || window.event,
    // 滚动条横向移动部分、竖向移动部分
    sLeft = getScrollOffset().left,
    sTop = getScrollOffset().top,
    // 当前页面文档偏移量
    cLeft = document.documentElement.clientLeft || 0,
    cTop = document.documentElement.clientTop || 0;

    return {
      x: sLeft + e.clientX - cLeft,
      y: sTop + e.clientY - cTop
    }
}

/**
 * 
 * @param {HTMLElement} elem HTML元素 
 */
function elemDrag(elem)	{
  var x,
    y,
    wWidth = getViewSize().width,
    wHeight = getViewSize().height,
    eWidth = parseInt(getStyles(elem, 'width')),
    eHeight = parseInt(getStyles(elem, 'height'));
  addEvent(elem, 'mousedown', function(e) {
    var e = e || window.event;
    x = pagePos(e).X - parseInt(getStyles(this, 'left'));
    y = pagePos(e).Y - parseInt(getStyles(this, 'top'));

    addEvent(document, 'mousemove', mouseMove);
    addEvent(document, 'mouseup', mouseUp);
    // 避免冒泡事件
    cancelBubble(e);
    // 避免默认事件
    preventDefaultEvent(e);
  }) 

  function mouseMove(e) {				
    var e = e || window.event,
      elemLeft = pagePos(e).X - x,
      elemTop = pagePos(e).Y - y;
    
    if (elemLeft < 0) {
      elemLeft = 0;
    } else if (elemLeft > wWidth - eWidth) {
      elemLeft = wWidth - eWidth - 1;
    }

    if (elemTop < 0) {
      elemTop = 0;
    } else if (elemTop > wHeight - eHeight) {
      elemTop = wHeight - eHeight - 1;
    }

    elem.style.left = elemLeft + 'px';
    elem.style.top = elemTop + 'px';				
  }

  function mouseUp(e) {
    var e = e || window.event;
    removeEvent(document, 'mousemove', mouseMove);
    removeEvent(document, 'mouseup', mouseUp);
  }
}